package com.qr.backend.service.impl;

import com.qr.backend.entity.Account;
import com.qr.backend.mapper.UserMapper;
import com.qr.backend.service.AuthorizeService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author : jinxiaobo
 * @date : 2023年12月21日 10:03:08
 * @description :
 */
@Slf4j
@Service
public class AuthorizeServiceImpl implements AuthorizeService {

    @Resource
    private UserMapper userMapper;

    @Value("${spring.mail.username}")
    private String username;

    //自动注入发送邮件的工具
    @Resource
    private MailSender mailSender;

    //redis工具类
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 密码加密器,不能注入这块,否则会出现循环依赖
     * ┌─────┐
     * |  securityConfiguration
     * ↑     ↓
     * |  authorizeServiceImpl
     * └─────┘
     */
    private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

    @Override
    public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
        if (null == usernameOrEmail)
            throw new UsernameNotFoundException("用户名为空");
        Account account = userMapper.findAccountByNameOrEmail(usernameOrEmail);
        if (null == account)
            throw new UsernameNotFoundException("用户名或密码错误");
        //构建一个用户对象返回
        return User.withUsername(account.getUserName()).password(account.getPassWord()).roles("admin").build();
    }

    /**
     * 发送邮件
     *
     * @param email
     * @return
     */
    @Override
    public String sendValidateEmail(String email, String sessionId, boolean hasCount) {
        //hasCount防止拿着注册的验证码去做了修改密码的操作
        String key = "email:" + sessionId + ":" + email + ":" + hasCount;
        //有可能你判断完事后，执行下面的代码正好过期了，导致expire为null,所以要判空
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
            Long expire = Optional.ofNullable(stringRedisTemplate.getExpire(key, TimeUnit.SECONDS)).orElse(0L);
            if (expire > 120) {
                return "验证码已发送,请注意查收";
            }
        }
        //false表示没有账户,如果是false就去判断账户存不存在
        Account account = userMapper.findAccountByNameOrEmail(email);
        if (hasCount && account == null) return "邮箱账户不存在";
        if (!hasCount && account != null) return "邮箱账户已经存在,请使用账户登入";
        /**
         * 生成邮箱验证码
         * 将邮箱信息放入到redis当中,key为邮箱地址,value为过期时间,过期时间三分钟
         * 发送验证码到指定邮箱
         */
        Random random = new Random();
        //生成六位数验证码
        int verifyCode = random.nextInt(899999) + 100000;
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(username);
        message.setTo(email);
        message.setSubject("观JB科技");
        message.setText("验证码:" + verifyCode);
        try {
            mailSender.send(message);
            ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
            valueOperations.set(key, String.valueOf(verifyCode), 180, TimeUnit.SECONDS);
        } catch (MailException e) {
            log.error("发送邮件失败:{}", e);
            return "发送失败";
        }
        return "发送成功";
    }


    /**
     * 验证并注册,高并发情况也有问题,就是同一时间连个用户使用相同的邮箱进行注册,同时进入这个方法
     * 补充步骤,如果用户名已经存在也不让进行注册,用户名和邮箱必须都是唯一的
     * @param username
     * @param password
     * @param email
     * @param code
     * @return
     */
    @Override
    public String validateAndRegister(String username, String password, String email, String code, String sessionId) {
        String key = "email:" + sessionId + ":" + email + ":true";
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
            String emailVerifyCode = stringRedisTemplate.opsForValue().get(key);
            if (null == emailVerifyCode) {
                return "验证码失效请重新获取";
            }
            if (emailVerifyCode.equals(code)) {
                //验证码使用过后清掉
                stringRedisTemplate.delete(key);
                //注册用户
                String encodePwd = encoder.encode(password);
                int result = userMapper.createAccount(username, encodePwd, email);
                if (result > 0) {
                    return "新增用户成功";
                }
                return "服务器异常";
            } else {
                return "验证码错误";
            }
        } else {
            return "请先获取邮箱验证码";
        }
    }

    /**
     * 更新密码第一步,发送验证码校验redis中是否有验证码,校验通过往session中打标
     *
     * @param email
     * @param code
     * @param httpSession
     * @return
     */
    @Override
    public String validateEmailCode(String email, String code, HttpSession httpSession) {
        String key = "email:" + httpSession.getId() + ":" + email + ":true";
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(key))) {
            String upCode = stringRedisTemplate.opsForValue().get(key);
            if (null == upCode) return "验证码失效";
            if (upCode.equals(code)) {
                //验证码使用过后清掉
                stringRedisTemplate.delete(key);
                //往session中进行打标
                httpSession.setAttribute("reset-password", email);
                return "验证通过";
            } else {
                return "验证码错误";
            }
        } else {
            return "请先获取验证码";
        }
    }

    /**
     * 更新密码
     *
     * @param password
     * @param email
     * @return
     */
    @Override
    public boolean resetPassWord(String password, String email) {
        String encodePwd = encoder.encode(password);
        int result = userMapper.resetPassWord(encodePwd, email);
        if (result > 0) {
            return true;
        }
        return false;
    }

}